//The lock system of Shared Takeaway Box Project 
/**************************************************************************
 
    Copyright (C) 2021  Flactis

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

	To contact with Flactis, please see <https://gitee.com/sunkenstar>.

***************************************************************************/

#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <stdlib.h>
#include <Keypad.h>
#include <string.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <HTTPClient.h>

//Misc Macro Definition
#define DEFAULT_STASSID ""//Wifi name					
#define DEFAULT_STAPSW ""//Wifi password
#define WRONG_PASSWORD 1//for the function wrongpass' parameter
#define WRONG_ORDER_NUMBER 0 //for the function wrongpass' parameter
#define INITIAL_SEND 1//for the function sendingPassword's parameter
#define REMINDING_SEND 0//for the function sendingPassword's parameter

//Pin Definition
static const int lock = 13;//the lock uses pin D13;
static const int ScreenSCL = 22;//the screen ScreenSCL links to D22;
static const int ScreenSDA = 21;//the screen ScreenSDA links to D21;

//The status of the controller.
//*=generate the password, display for 5 secs, and lock the * button; 
//#=start inputting the password, if correct unlock the box.
static char status;

//Network Variants
static const int userNum = 2;//The number of users, currently myself.			
static char uid[userNum][64]= {"User1's Private Key","User2's Private Key"};	  	
String type = "1";//DO NOT EDIT IT because its usage is unknown.									    
String device = "共享外卖箱";
String initMsg = "您的外卖箱密码是";
String initMsg2 = "祝您用餐愉快!";	
String remindingMessage = "您的外卖已经送达";
String moments="分钟啦";
String remindingMessage2 = "请趁热食用";						
static int delaytime = 0;									 
String ApiUrl = "http://ai.bemfa.com/api/wechat/v1/"; //Default API.
static int lastWiFiCheckTick = 0;

//Misc Variants
static char c_order[5];//The order number in string. fxl is 1207. zqr is 0130
static int i_order;//Order number, convesed into int.
static int usercode;//The user's code. fxl is 1.
static int password;//The password randomly generated as integer.
static char passline[5];//The password processed into char[].
static char input[5];//The input code, to be compared with the password.
static bool locked;//A flag to decide whether the box is locked.
static long initialTime=-1;//The first time when the password is generated.
static long lastTime=-1;//The previous sending time.
static const long deltaTime=60000;//After this period will send a reminding message.
static long i_timePassed;//The time passed as integer.
static char c_timePassed[4];//The time passed as char[]

//Keypad Initialization
static const byte ROWS = 4;
static const byte COLS = 3;
static char keymap[ROWS][COLS] = {
	{'1', '2', '3'},
	{'4', '5', '6'},
	{'7', '8', '9'},
	{'*', '0', '#'}};
byte rowPins[ROWS] = {12, 14, 27, 26};
byte colPins[COLS] = {25, 33, 32};
Keypad DasKeypad = Keypad(makeKeymap(keymap), rowPins, colPins, ROWS, COLS);

//Screen Initialization
//Please remember that the contents and location of the display shall be altered.
Adafruit_SSD1306 display(128,64,&Wire,-1);

//WiFi Reconnection
void startSTA()
{
	WiFi.disconnect();
	WiFi.mode(WIFI_MODE_STA);
	WiFi.begin(DEFAULT_STASSID, DEFAULT_STAPSW);
}

//WiFi Detection
void doWiFiTick()
{
	if (WiFi.status() != WL_CONNECTED)
	{
		if (millis() - lastWiFiCheckTick > 1000)
		{
			lastWiFiCheckTick = millis();
			startSTA();
		}
	}
}

//Send Password to the User
//isInitialSend==1 means the message is sent first time.
//isInitialSend==0 means to send the reminding message.
//Send Password to the User
//isInitialSend==1 means the message is sent first time.
//isInitialSend==0 means to send the reminding message.
void sendingPassword(bool isInitialSend)
{
	String user=uid[usercode];
	HTTPClient http;
	String postData;
	if(isInitialSend) 
		postData = "uid=" + user + "&type=" + type + "&time=" + delaytime + "&device=" + device + "&msg=" + initMsg + passline + "&msg2=" +initMsg2;
	else
		postData = "uid=" + user + "&type=" + type + "&time=" + delaytime + "&device=" + device + "&msg=" + remindingMessage + c_timePassed + moments + "&msg2=" +remindingMessage2;
	http.begin(ApiUrl);													 //Specify request destination
	http.addHeader("Content-Type", "application/x-www-form-urlencoded"); //Specify content-type header
	int httpCode = http.POST(postData);									 //Send the request
	String payload = http.getString();									 //Get the response payload
	Serial.println(httpCode);											 //Print HTTP return code
	Serial.println(payload);											 //Print request response payload
	http.end();															 //Close connection
	Serial.println("send success");
}


//Order Input
void inputOrder()
{
	long t0=millis();
	display.clearDisplay();
	display.setTextSize(1);
	display.setTextColor(WHITE);
	display.setCursor(10,10);
	display.println(F("Input the Order Number"));
	display.display();
	for(int i=0;i<4;i++)
	{
		if(millis()-t0>10000)
		{
			memset(c_order,0,sizeof(c_order));
			break;
		}
		char keyInput = DasKeypad.getKey();
		if(keyInput)
		{
			c_order[i]=keyInput;
			//Serial.print("Estel");
			//Serial.println(input[i]);
			display.setTextSize(2);
			display.setTextColor(WHITE);
			display.setCursor(10+10*(i+1),50);
			display.println(keyInput);
			display.display();
		}
		else
		{
			i--;
			continue;
		}
	}
	i_order=(c_order[3]-'0')+10*(c_order[2]-'0')+100*(c_order[1]-'0')+1000*(c_order[0]-'0');
	Serial.println(i_order);
	switch(i_order)
	{
		case 1207:
		usercode=0;
		break;
		case 130:
		usercode=1;
		break;
		default:
		usercode=-1;
		break;
	}
}

//Password Generator
void genPass()
{
	srand((unsigned int)micros()+i_order);
	password = rand();
	passline[0] = (password / 1000) % 10 + '0';
	passline[1] = (password / 100) % 10 + '0';
	passline[2] = (password / 10) % 10 + '0';
	passline[3] = (password / 1) % 10 + '0';
	passline[4] = 0;
}

//Welcome Screen
void welcome()
{
	display.clearDisplay();
	display.setTextSize(2);
	display.setTextColor(WHITE);
	display.setCursor(22, 10);
	display.println(F("Welcome!"));
	display.setTextSize(1);
	display.setTextColor(WHITE);
	display.setCursor(10,35);
	display.println(F("Shared Takeaway Box 1.0.0"));
	display.display();
}

//Lock the Box
//Please mention that the box is in fact locked manually.
//This part is simply telling the deliver he can leave the box now.
void boxLocker()
{
	display.clearDisplay();
	display.setTextSize(2);
	display.setTextColor(WHITE);
	display.setCursor(20,10);
	display.println(F("Password SENT"));
	display.display();
	delay(1000);
	welcome();
}

//Unlock the box
void boxUnlocker()
{
	digitalWrite(lock,HIGH);
	delay(1000);
	digitalWrite(lock,LOW);
	display.clearDisplay();
	display.setTextSize(1);
	display.setTextColor(WHITE);
	display.setCursor(20,10);
	display.println(F("Have a good meal!"));
	display.display();
	delay(5000);
	welcome();
}

//Protocol for Wrong Password/User Number
//isCode==0 means wrong order number. 
void wrongPass(bool isCode)
{
	display.clearDisplay();
	display.setTextSize(1);
	display.setTextColor(WHITE);
	display.setCursor(10,10);
	if(isCode)
		display.println(F("Wrong Password!"));
	else
		display.println(F("Order Not Exist!"));
	display.setTextSize(1);
	display.setTextColor(WHITE);
	display.setCursor(10,50);
	display.println(F("Please Reassure"));
	display.display();
	delay(2000);
}

//Key reader
void readKey()
{
	long t0=millis();
	display.clearDisplay();
	display.setTextSize(1);
	display.setTextColor(WHITE);
	display.setCursor(10,10);
	display.println(F("Input the password"));
	display.display();
	for(int i=0;i<4;i++)
	{
		if(millis()-t0>10000)
		{
			memset(input,0,sizeof(input));
			break;
		}
		char keyInput = DasKeypad.getKey();
		if(keyInput)
		{
			input[i]=keyInput;
			display.setTextSize(2);
			display.setTextColor(WHITE);
			display.setCursor(10+10*(i+1),50);
			display.println(F("*"));
			display.display();
		}
		else
		{
			i--;
			continue;
		}
	}
}

//Show License
void license()
{
	Serial.println("The Lock System of Shared Takeaway Box Project");
	Serial.println("Copyright (C) 2021 Flactis");
	Serial.println("This program comes with ABSOLUTELY NO WARRANTY;");
	Serial.println("This is free software, and you are welcome to redistribute it under certain conditions.");
	Serial.println("This program is under GPL 3.0 License.");
	Serial.println("For further information on these, please check http://www.gnu.org/licenses/");
}

//Last Preparation
void setup()
{	
	Serial.begin(115200);
	license();
	WiFi.mode(WIFI_MODE_NULL); //Prevents reconnection issue (taking too long to connect)
	delay(1000);
	WiFi.mode(WIFI_MODE_STA); //This line hides the viewing of ESP as WiFi hotspot
	WiFi.begin(DEFAULT_STASSID, DEFAULT_STAPSW); //Connect to your WiFi router
	Serial.println("");
	Serial.print("Connecting");
	while (WiFi.status() != WL_CONNECTED)
	{
		delay(500);
		Serial.print(".");
	}
	Serial.println("");
	Serial.print("Connected to ");
	Serial.println(DEFAULT_STASSID);
	Serial.print("IP address: ");
	Serial.println(WiFi.localIP()); 
	pinMode(lock,OUTPUT);
	display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
	display.clearDisplay();
	display.display();
}

//Main Loop
void loop()
{
	char keyInput;
	bool isIncorrect=1;
	welcome();
	if(lastTime>=0&&millis()-lastTime>=deltaTime)
	{
		lastTime=millis();
		i_timePassed=lastTime-initialTime;
		i_timePassed/=60000;
		c_timePassed[0] = (i_timePassed / 100) % 10 + '0';
		c_timePassed[1] = (i_timePassed / 10) % 10 + '0';
		c_timePassed[2] = (i_timePassed / 1) % 10 + '0';
		c_timePassed[3] = 0;
		sendingPassword(REMINDING_SEND);
	}
	keyInput = DasKeypad.getKey();
	if(keyInput)
		status=keyInput;
	else return;
	switch(status)
	{
		case '*':
		if(locked==1) return;
		Serial.println("Status *");
		while(isIncorrect)
		{
			inputOrder();
			if(c_order[0]==0) return;
			if(usercode==-1)
				wrongPass(WRONG_ORDER_NUMBER);
	        else isIncorrect=0;			
		}
		genPass();
		sendingPassword(INITIAL_SEND);
		boxLocker();
		memset(c_order,0,sizeof(c_order));
		initialTime=millis();
		lastTime=initialTime;
		locked=1;
		break;
		case '#':
		if(locked==0) return;
		Serial.println("Status #");
		while(isIncorrect)
		{
			readKey();
			if(input[0]==0) return;
			if(strcmp(input,passline)==0)
			{
				boxUnlocker();
				isIncorrect=0;
			}
			else
				wrongPass(WRONG_PASSWORD);
			memset(input,0,sizeof(input));
		}
		locked=0;
		initialTime=-1;
		lastTime=-1;
		break;
		default:
		return;
	}
	delay(100);
}